import { ref } from 'vue';
import { getQuickJS, type QuickJSWASMModule, type QuickJSContext, type QuickJSHandle } from 'quickjs-emscripten';
import { Scope } from 'quickjs-emscripten';
import { babelService } from './babelService'; // Import Babel service
import { useUniversalVisualizerSettingsStore } from '@/stores/universalVisualizerSettingsStore'; // Import store

const isInitialized = ref(false);
let initializePromise: Promise<QuickJSWASMModule> | null = null;
let QuickJS: QuickJSWASMModule | null = null;

// --- START: Paste Serializer Source Code ---
// IMPORTANT: In a real build setup, load this using import('.../quickjsUniversalSerializer.ts?raw')
// or similar mechanism to get the raw JS source code.
const quickJsSerializerSource = `
/**
 * @fileoverview Universal Serializer for QuickJS Environment.
 * Converts arbitrary JavaScript values into a JSON-compatible Intermediate Representation (IR),
 * handling complex types (Date, RegExp, Map, Set, BigInt, Symbol keys, TypedArrays, etc.)
 * and circular references.
 *
 * IMPORTANT: This code is intended to run *inside* QuickJS. It must be self-contained
 * and use only JavaScript features available in the target QuickJS version (ES2020+).
 * It CANNOT use browser/Node APIs or external libraries like lodash.
 */

// --- START: Embedded Base64 Helpers (No 'btoa' in QuickJS) ---
const base64Encode = (function() {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

    function encode(inputBytes) {
        let base64 = '';
        let i = 0;
        const len = inputBytes.length;

        while (i < len) {
            const byte1 = inputBytes[i++];
            const byte2 = i < len ? inputBytes[i++] : NaN;
            const byte3 = i < len ? inputBytes[i++] : NaN;

            const enc1 = byte1 >> 2;
            const enc2 = ((byte1 & 3) << 4) | (byte2 >> 4);
            const enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
            const enc4 = byte3 & 63;

            base64 += chars.charAt(enc1);
            base64 += chars.charAt(enc2);
            base64 += isNaN(byte2) ? '=' : chars.charAt(enc3);
            base64 += isNaN(byte3) ? '=' : chars.charAt(enc4);
        }
        return base64;
    }

    return {
        bytesToBase64: function(bytes) {
            if (!(bytes instanceof Uint8Array)) {
                if (bytes instanceof ArrayBuffer) {
                    bytes = new Uint8Array(bytes);
                } else {
                    try {
                         bytes = Uint8Array.from(bytes);
                    } catch (e) {
                        throw new Error("Input must be Uint8Array, ArrayBuffer, or an array of bytes (0-255).");
                    }
                }
            }
            return encode(bytes);
        }
    };
})();
// --- END: Embedded Base64 Helpers ---

function createSerializationState() {
    return {
        visitedToReferenceId: new Map(),
        nextReferenceId: 1,
    };
}

function serializeToUniversalIR(data, state) {
    state = state || createSerializationState();
    const type = typeof data;
    if (data === null) return null;
    if (type === 'string' || type === 'number' || type === 'boolean') return data;
    if (type === 'undefined') return { __universalDataType__: 'Undefined' };
    if (type === 'bigint') return { __universalDataType__: 'BigInt', value: data.toString() };

    if (type === 'symbol') {
        if (state.visitedToReferenceId.has(data)) return { __universalDataType__: 'CircularRef', __universalRefId__: state.visitedToReferenceId.get(data) };
        const refId = state.nextReferenceId++;
        state.visitedToReferenceId.set(data, refId);
        return { __universalDataType__: 'Symbol', description: data.description, __universalRefId__: refId };
    }

    if (state.visitedToReferenceId.has(data)) return { __universalDataType__: 'CircularRef', __universalRefId__: state.visitedToReferenceId.get(data) };
    const currentRefId = state.nextReferenceId++;
    state.visitedToReferenceId.set(data, currentRefId);

    if (data instanceof Date) {
        if (isNaN(data.getTime())) {
            return { __universalDataType__: 'Date', __universalRefId__: currentRefId, value: null, invalid: true };
        } else {
            return { __universalDataType__: 'Date', __universalRefId__: currentRefId, value: data.toISOString() };
        }
    }
    if (data instanceof RegExp) return { __universalDataType__: 'RegExp', __universalRefId__: currentRefId, source: data.source, flags: data.flags };

    if (data instanceof Error) {
        let causeIR = undefined;
        if ('cause' in data && data.cause !== undefined) causeIR = serializeToUniversalIR(data.cause, state);
        return { __universalDataType__: 'Error', __universalRefId__: currentRefId, name: data.name, message: data.message, stack: data.stack, cause: causeIR };
    }

    if (data instanceof ArrayBuffer) {
        try { return { __universalDataType__: 'ArrayBuffer', __universalRefId__: currentRefId, base64: base64Encode.bytesToBase64(data) }; }
        catch (e) { return { __universalDataType__: 'Error', __universalRefId__: currentRefId, name: 'SerializationError', message: 'Failed to encode ArrayBuffer' }; }
    }
    if (ArrayBuffer.isView(data) && !(data instanceof DataView)) {
        try { return { __universalDataType__: 'TypedArray', __universalRefId__: currentRefId, constructorName: data.constructor.name, base64: base64Encode.bytesToBase64(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)) }; }
        catch (e) { return { __universalDataType__: 'Error', __universalRefId__: currentRefId, name: 'SerializationError', message: 'Failed to encode TypedArray' }; }
    }

    if (data instanceof Map) {
        const entries = []; for (const [k, v] of data.entries()) entries.push([serializeToUniversalIR(k, state), serializeToUniversalIR(v, state)]);
        return { __universalDataType__: 'Map', __universalRefId__: currentRefId, entries: entries };
    }
    if (data instanceof Set) {
        const values = []; for (const v of data.values()) values.push(serializeToUniversalIR(v, state));
        return { __universalDataType__: 'Set', __universalRefId__: currentRefId, values: values };
    }
    if (data instanceof WeakMap) return { __universalDataType__: 'WeakMap', __universalRefId__: currentRefId };
    if (data instanceof WeakSet) return { __universalDataType__: 'WeakSet', __universalRefId__: currentRefId };

    if (Array.isArray(data)) {
        const values = data.map(item => serializeToUniversalIR(item, state));
        return { __universalDataType__: 'Array', __universalRefId__: currentRefId, values: values };
    }

    if (type === 'function') {
        let source = '[Function source unavailable]'; try { source = data.toString(); } catch (e) {}
        return { __universalDataType__: 'Function', __universalRefId__: currentRefId, source: source };
    }

    if (type === 'object') {
        const entries = []; const keys = Reflect.ownKeys(data);
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i]; const value = data[key]; let keyIR;
            if (typeof key === 'string') keyIR = key;
            else if (typeof key === 'symbol') keyIR = serializeToUniversalIR(key, state);
            else continue;
            const valueIR = serializeToUniversalIR(value, state);
            entries.push([keyIR, valueIR]);
        }
        return { __universalDataType__: 'Object', __universalRefId__: currentRefId, entries: entries };
    }

    console.warn('[Serializer] Unhandled data type:', type, data);
    let fallbackValue = '[Unserializable Data]'; try { fallbackValue = String(data); } catch (e) {}
    return { __universalDataType__: 'Unknown', __universalRefId__: currentRefId, value: fallbackValue };
}
`;
// --- END: Paste Serializer Source Code ---

/**
 * Initializes the QuickJS WASM module.
 * Ensures initialization only happens once.
 * 
 * @returns A promise that resolves with the QuickJS module instance when initialization is complete.
 */
async function initialize(): Promise<QuickJSWASMModule> {
    if (QuickJS) {
        return Promise.resolve(QuickJS);
    }
    if (initializePromise) {
        return initializePromise;
    }

    console.log('[QuickJsService] Initializing QuickJS WASM...');
    initializePromise = getQuickJS();

    initializePromise.then(module => {
        QuickJS = module;
        isInitialized.value = true;
        console.log('[QuickJsService] QuickJS WASM initialized successfully.');
    }).catch(error => {
        console.error('[QuickJsService] Failed to initialize QuickJS WASM:', error);
        QuickJS = null; // Ensure QuickJS is null on failure
        isInitialized.value = false; // Explicitly set to false on error
        // No need to reset initializePromise here, let subsequent calls retry getQuickJS
        // or handle the persistent error state.
    });
    // We don't reset initializePromise to null immediately in case of multiple parallel calls during init

    return initializePromise;
}

/**
 * Gets the initialized QuickJS module instance.
 * Will attempt to initialize if not already done.
 * 
 * @returns A promise resolving to the QuickJS module instance, or rejecting if initialization fails.
 */
async function getModule(): Promise<QuickJSWASMModule> {
    // Ensure initialization is triggered and awaited
    return initialize(); 
}

/**
 * Safely evaluates a JavaScript expression string within a QuickJS context.
 * Allows injecting a limited, safe context (variables/functions).
 * Handles basic JSON-compatible return types. -> NOW RETURNS SERIALIZED IR
 * Reads expression-specific memory and stack limits from the visualizer settings store.
 *
 * @param expressionString The JavaScript expression to evaluate.
 * @param executionContext An optional object containing key-value pairs to expose
 *                         as global variables in the QuickJS context.
 *                         Values can be primitives or functions (exposed via newFunction).
 * @returns A JSON string containing the serialized Intermediate Representation (IR) of the result.
 * @throws If QuickJS is not initialized or evaluation fails catastrophically.
 */
export async function evaluateExpression(expressionString: string, executionContext?: Record<string, any>): Promise<string> {
    const QuickJS = await getModule();
    let vm: QuickJSContext | undefined = undefined;
    let serializedResultJson: string | undefined = undefined;

    // Get settings store instance
    const visualizerSettings = useUniversalVisualizerSettingsStore();
    const memoryLimitBytes = (visualizerSettings.quickJsExprMemoryLimitMb || 2) * 1024 * 1024;
    const stackSizeKb = (visualizerSettings.quickJsExprStackSizeKb || 128) * 1024;

    // Inject serializer and prepare evaluation code
    const evaluationCode = `
        ${quickJsSerializerSource} // Inject serializer

        let __eval_result;
        let __eval_error;
        try {
            __eval_result = (${expressionString}); // Evaluate the user's expression
        } catch (e) {
            __eval_error = e; // Capture error
        }

        // Serialize result or error
        const serializedIr = serializeToUniversalIR(__eval_error !== undefined ? __eval_error : __eval_result);

        // If an error occurred during evaluation, wrap the serialized error
        if (__eval_error !== undefined) {
            JSON.stringify({ __universalExecutionError__: serializedIr });
        } else {
            JSON.stringify(serializedIr); // Stringify the IR of the result
        }
    `;

    try {
        vm = QuickJS.newContext();
        const currentVm = vm;

        await Scope.withScopeAsync(async (scope) => {
            if (!currentVm) throw new Error("QuickJS VM context was not properly initialized.");

            // Inject executionContext (same logic as before)
            if (executionContext) {
                for (const [key, value] of Object.entries(executionContext)) {
                    let handle: QuickJSHandle;
                     if (typeof value === 'function') {
                         handle = scope.manage(currentVm.newFunction(key, (...args) => {
                             try {
                                 const nativeArgs = args.map(arg => currentVm.dump(arg));
                                 const result = value(...nativeArgs);
                                 // Here, we might need to serialize the result before passing back?
                                 // For now, assume simple JSON results from host functions.
                                 // A more robust solution would serialize result here too.
                                 let resultJson: string;
                                 try {
                                     resultJson = JSON.stringify(result); // Basic stringify for host func result
                                 } catch (stringifyError) {
                                     console.error(`[QuickJsService] Error stringifying result for host function '${key}':`, stringifyError);
                                     return currentVm.undefined; // Return undefined on error
                                 }
                                 try {
                                     // Re-parse inside QuickJS to create a handle
                                     return currentVm.unwrapResult(currentVm.evalCode(`(${resultJson})`));
                                 } catch (evalError) {
                                      console.error(`[QuickJsService] Error re-evaluating host function result for '${key}':`, evalError);
                                      return currentVm.undefined;
                                 }
                             } catch (err) {
                                 console.error(`[QuickJsService] Error executing host function '${key}':`, err);
                                 return currentVm.undefined;
                             }
                         }));
                     } else {
                         // Attempt to stringify and re-evaluate simple values
                         try {
                            const valueJson = JSON.stringify(value);
                            handle = scope.manage(currentVm.unwrapResult(currentVm.evalCode(`(${valueJson})`)));
                         } catch(jsonError) {
                             console.error(`[QuickJsService] Failed to stringify/evaluate context value for key '${key}':`, jsonError);
                             handle = currentVm.undefined; // Use undefined on failure
                         }
                    }
                     if (handle && handle.alive) {
                         currentVm.setProp(currentVm.global, key, handle);
                     } else {
                         console.warn(`[QuickJsService] Handle for context key '${key}' was not created or invalid.`);
                     }
                }
            }

            // Set limits
            console.log(`[QuickJsService - evaluateExpression] Applying EXPRESSION limits - Memory: ${memoryLimitBytes} bytes, Stack: ${stackSizeKb} bytes`);
            currentVm.runtime.setMemoryLimit(memoryLimitBytes);
            currentVm.runtime.setMaxStackSize(stackSizeKb);

            // Evaluate the combined code (expression + serialization)
            console.log(`[QuickJsService] Evaluating expression wrapper: ${expressionString}`);
            const resultHandle = currentVm.unwrapResult(currentVm.evalCode(evaluationCode));
            const resultJsonString = currentVm.dump(resultHandle);
            console.log("[QuickJsService] Evaluation successful, IR JSON length:", resultJsonString?.length);

            if (typeof resultJsonString !== 'string') {
                 throw new Error('QuickJS expression evaluation did not return a string (expected JSON IR). Result: ' + String(resultJsonString));
            }
            serializedResultJson = resultJsonString;

        }); // End Scope.withScopeAsync

    } catch (err) {
        console.error('[QuickJsService] Error during expression evaluation:', err);
        // Serialize the error itself if possible
        const errorIr = { // Manually create basic error IR if serialization fails
            __universalDataType__: 'Error',
            name: err instanceof Error ? err.name : 'UnknownError',
            message: err instanceof Error ? err.message : String(err),
            stack: err instanceof Error ? err.stack : undefined,
        };
        serializedResultJson = JSON.stringify({ __universalExecutionError__: errorIr });
        // Do not re-throw, return the serialized error
    } finally {
        if (vm && vm.alive) {
             vm.dispose();
        }
    }

    // Ensure we always return a string (even if it's a serialized error)
    return serializedResultJson || JSON.stringify({
        __universalExecutionError__: {
             __universalDataType__: 'Error', name: 'InternalError', message: 'Evaluation failed to produce a result.'
        }
    });
}

/**
 * Executes a standard JavaScript/TypeScript script string in QuickJS,
 * first transpiling TS to JS using Babel service.
 * Attempts to return the serialized Intermediate Representation (IR) of its default export.
 * Assumes the script is self-contained and doesn't rely on external imports or APIs.
 * Reads script-specific memory and stack limits from the visualizer settings store.
 *
 * @param scriptContent The complete script content as a string (can be TS).
 * @returns A promise resolving to a JSON string containing the serialized IR of the default export.
 * @throws If Babel transpilation fails or QuickJS initialization fails catastrophically.
 */
async function executeScriptAndGetSerializedIr(scriptContent: string): Promise<string> {
    let jsCode: string;
    try {
        // Step 1: Transpile TS to JS using Babel Service
        console.log('[QuickJsService] Starting script transpilation via @babel/standalone...');
        jsCode = babelService.transpileTsToJs(scriptContent);
        console.log('[QuickJsService] @babel/standalone transpilation successful.');
    } catch (transpileError) {
        console.error('[QuickJsService] @babel/standalone transpilation failed before QuickJS execution:', transpileError);
        const error = new Error(`@babel/standalone Transpilation Failed: ${transpileError instanceof Error ? transpileError.message : String(transpileError)}`);
        // Serialize and wrap the transpilation error
        const errorIr = { __universalDataType__: 'Error', name: error.name, message: error.message, stack: error.stack };
        return JSON.stringify({ __universalExecutionError__: errorIr });
    }

    // Get settings store instance
    const visualizerSettings = useUniversalVisualizerSettingsStore();
    const memoryLimitBytes = (visualizerSettings.quickJsMemoryLimitMb || 10) * 1024 * 1024;
    const stackSizeKb = (visualizerSettings.quickJsStackSizeKb || 512) * 1024;
    console.log(`[QuickJsService - executeScript] Applying SCRIPT limits - Memory: ${memoryLimitBytes} bytes, Stack: ${stackSizeKb} bytes`);

    const QuickJS = await getModule();
    let vm: QuickJSContext | undefined = undefined;
    let serializedResultJson: string | undefined = undefined;

    // Step 2: Prepare the wrapped script using transpiled JS + Serializer
    const wrappedScript = `
        ${quickJsSerializerSource} // Inject the serializer

        let __module_result;
        let __script_error;
        let exports = {};
        let module = { exports: exports };

        try {
            // --- User Script Execution ---
            {
                ${jsCode} // The user's transpiled code
            }
            // --- Determine Exported Value ---
            if (typeof module.exports !== 'undefined' && typeof module.exports === 'object' && Object.keys(module.exports).length > 0 && module.exports !== exports) {
                 __module_result = module.exports;
            } else if (typeof exports.default !== 'undefined') {
                __module_result = exports.default;
            } else if (typeof computationResult !== 'undefined') {
                __module_result = computationResult;
            } else if (typeof configData !== 'undefined') {
                __module_result = configData;
            } else {
                const globalKeys = Object.keys(globalThis || this || {});
                const potentialExports = globalKeys.filter(k => ![
                     'console', 'JSON', 'module', 'exports',
                     '__module_result', '__script_error',
                     'serializeToUniversalIR', 'createSerializationState', 'base64Encode' // Exclude injected helpers
                     ].includes(k) && !k.startsWith('__')); // Basic exclusion
                 if (potentialExports.length === 1 && typeof (globalThis || this || {})[potentialExports[0]] !== 'undefined') {
                     __module_result = (globalThis || this || {})[potentialExports[0]];
                 } else if (potentialExports.length === 0 && Object.keys(exports).length === 0 && Object.keys(module.exports).length === 0) {
                      // If nothing obvious was exported, maybe the script just produced side effects or returned undefined?
                      __module_result = undefined;
                 } else {
                      throw new Error("Could not determine the exported value (tried module.exports, exports.default, computationResult, configData, and single global variable).");
                 }
            }
            // --- END Determine Exported Value ---

        } catch (e) {
            __script_error = e instanceof Error ? e : new Error(String(e)); // Capture script error
        }

        // --- Custom Serialization Step ---
        const valueToSerialize = __script_error !== undefined ? __script_error : __module_result;
        const serializedIr = serializeToUniversalIR(valueToSerialize); // Use our custom serializer

        // If an error occurred during script execution, wrap the serialized error
        if (__script_error !== undefined) {
            JSON.stringify({ __universalExecutionError__: serializedIr });
        } else {
            JSON.stringify(serializedIr); // Stringify the IR of the result
        }
    `;

    try {
        vm = QuickJS.newContext();
        const currentVm = vm;

        await Scope.withScopeAsync(async (scope) => {
            if (!currentVm) throw new Error("QuickJS VM context was not properly initialized.");

            // Set limits
            currentVm.runtime.setMemoryLimit(memoryLimitBytes);
            currentVm.runtime.setMaxStackSize(stackSizeKb);

            console.log('[QuickJsService] Executing full script wrapper with custom serialization...');
            const resultHandle = currentVm.unwrapResult(currentVm.evalCode(wrappedScript));
            const resultJsonString = currentVm.dump(resultHandle);
            console.log('[QuickJsService] Script wrapper execution finished. IR JSON length:', resultJsonString?.length);

            if (typeof resultJsonString !== 'string') {
                 throw new Error('QuickJS script execution did not return a string (expected JSON IR). Result: ' + String(resultJsonString));
            }
            serializedResultJson = resultJsonString; // Store the resulting JSON string

        }); // End Scope.withScopeAsync

    } catch (err) {
        console.error('[QuickJsService] Error during full script execution or serialization wrapper:', err);
        // Serialize the error itself if possible
        const errorIr = { // Manually create basic error IR if serialization fails
             __universalDataType__: 'Error',
             name: err instanceof Error ? err.name : 'UnknownError',
             message: err instanceof Error ? err.message : String(err),
             stack: err instanceof Error ? err.stack : undefined,
        };
        serializedResultJson = JSON.stringify({ __universalExecutionError__: errorIr });
        // Do not re-throw, return the serialized error
    } finally {
        if (vm && vm.alive) {
            vm.dispose();
        }
    }

     // Ensure we always return a string (even if it's a serialized error)
    return serializedResultJson || JSON.stringify({
        __universalExecutionError__: {
             __universalDataType__: 'Error', name: 'InternalError', message: 'Script execution failed to produce a result.'
        }
    });
}

// TODO: Add functions for creating contexts, evaluating code safely,
// managing lifetimes, and potentially exposing host functions.
// Example structure:
/*
async function evaluateCodeInContext(code: string, contextSetup?: (vm: QuickJSContext) => void): Promise<any> {
    const QJS = await getModule();
    const vm = QJS.newContext();
    let resultValue: any = undefined;
    let errorOccurred: Error | null = null;

    try {
        // Setup context (expose functions, set globals etc.)
        if (contextSetup) {
            contextSetup(vm);
        }

        const result = vm.evalCode(code);

        if (result.error) {
            errorOccurred = new Error(`QuickJS Execution failed: ${vm.dump(result.error)}`);
            result.error.dispose();
        } else {
            resultValue = vm.dump(result.value);
            result.value.dispose();
        }
    } catch (err) {
        errorOccurred = err instanceof Error ? err : new Error(String(err));
    } finally {
        // Ensure vm is disposed even if errors occur
        if (vm) {
            vm.dispose();
        }
    }
    
    if (errorOccurred) {
        throw errorOccurred;
    }
    return resultValue;
}
*/

// Export the service functions
export const quickJsService = {
    initialize,
    getModule,
    isInitialized: isInitialized,
    evaluateExpression, // Keep this, now returns serialized IR string
    executeScriptAndGetSerializedIr, // <-- Correctly export the new function name
    // evaluateCodeInContext, // Add evaluation function later
}; 